﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Composite.Data;
using LCA.Common.Extensions;
using System.Text.RegularExpressions;
using System.Web;

namespace LCA.Common.Calculation
{
    public class CalculationManager
    {
        private Guid SystemId;
        private List<ImpactFactorCalculationResult> list;
        private List<HotspotResult> hotspotList;

        public CalculationManager(Guid SystemId)
        {
            this.SystemId = SystemId;
            this.list = new List<ImpactFactorCalculationResult>();
            this.hotspotList = new List<HotspotResult>();
        }

        public void Calculate()
        {
            ProcessSystem(this.SystemId, 1, new List<ParentRow>());

            // Do the fix for hotspot of systems
            PostProcessHotspot_OnSystem();
            
        }

        private void PostProcessHotspot_OnSystem()
        {
            List<HotspotResult> newSystems = new List<HotspotResult>();

            // Sum the emission from 'System-Raw' By Id and save to 'newSystems'
            foreach (HotspotResult resultRow in hotspotList)
            {
                
                var result = resultRow.Rows.Where(y => y.ItemType == "System-Raw").GroupBy(x => x.ItemName.Split('|')[0])
                                .Select(g => new { Id = g.Key, Sum = g.Sum(x => x.Emission), Source = g.Select(x => x.Source).FirstOrDefault() });

                if (result.Count() > 0)
                {
                    HotspotResult systemRow = new HotspotResult();
                    systemRow.FactorId = resultRow.FactorId;
                    systemRow.FactorName = resultRow.FactorName;
                    systemRow.Rows = new List<HotspotRow>();

                    DataConnection conn = new DataConnection();

                    foreach (var item in result)
                    {
                        HotspotRow row = new HotspotRow();
                        Guid systemId = item.Id.Split('|')[0].ToGuid().Value;
                        LCA.Data.LCASystem system = conn.Get<LCA.Data.LCASystem>().Where(x => x.Id == systemId).FirstOrDefault();

                        row.ItemUrl = GetSystemUrl("System", systemId);
                        row.ItemType = "System";
                        row.Source = item.Source;
                        row.Emission = item.Sum;
                        row.ItemName = system.Name;

                        systemRow.Rows.Add(row);
                    }


                    newSystems.Add(systemRow);
                }

            }

            // Add System to 'hotspotList'
            foreach (HotspotResult systemRow in newSystems)
            {
                HotspotResult target = hotspotList.Where(x => x.FactorId == systemRow.FactorId).FirstOrDefault();

                foreach(HotspotRow row in systemRow.Rows)
                {
                    target.Rows.Add(row);
                }

            }
            // Remove any item that has 'System-Raw' type
            // Ex. authorsList.RemoveAll(x => x.FirstName == "Bob");
            foreach (HotspotResult resultRow in hotspotList)
            {
                resultRow.Rows.RemoveAll(x => x.ItemType == "System-Raw");
            }

        }

        public void ProcessSystem(Guid systemId, decimal systemAmount, List<ParentRow> ParentList)
        {
            ProcessInput(systemId, systemAmount, ParentList);
            ProcessOutput(systemId, systemAmount, ParentList);
            ProcessTransportA(systemId, systemAmount, ParentList);
            ProcessTransportB(systemId, systemAmount, ParentList);
        }

        public void ProcessInput(Guid systemId, decimal systemAmount, List<ParentRow> ParentList)
        {
            dynamic inputList = null;
            using (DataConnection conn = new DataConnection())
            {
                inputList = (from input in conn.Get<LCA.Data.LCASystemInput>()
                             join u in conn.Get<LCA.Data.Unit>() on input.Unit equals u.Id into joined
                             from j in joined.DefaultIfEmpty()
                             where input.RelatedSystem == systemId
                             select new
                             {
                                 RefItemType = input.RefItemType,
                                 RefItemId = input.RefItemId,
                                 Amount = input.Amount,
                                 Unit = (Guid?) j.Id
                             }).ToList();


                foreach (var item in inputList)
                {
                    if (item.RefItemType == "Flow")
                    {
                        ProcessImpactFactor_OnFlow(item.RefItemId, ((String) item.Amount).ToDecimal().Value, item.Unit, systemAmount, Source.Input, ParentList);
                    }
                    else if (item.RefItemType == "System")
                    {
                        Guid relatedSystemId = item.RefItemId;
                        LCA.Data.LCASystem system = conn.Get<LCA.Data.LCASystem>().Where(s => s.Id == relatedSystemId).FirstOrDefault();
                        Guid? systemUnitId = system.Unit;
                        decimal relativeAmount = ((String)item.Amount).ToDecimal().Value / system.Amount.ToDecimal().Value;
                        decimal multiplier = GetMultiplier_OnTargetUnit(conn, item.Unit, systemUnitId);

                        
                        // Recursive Call. Watch this.
                        decimal SystemAmount = relativeAmount * multiplier * systemAmount;
                        ParentList.Add(new ParentRow { Source = Source.Input, ParentId = item.RefItemId });
                        ProcessSystem(item.RefItemId, SystemAmount, ParentList);

                        // Remove Last item
                        if (ParentList.Count > 0)
                            ParentList.Remove(ParentList.Last());
                    }

                }

            }

            
        }

        private void ProcessOutput(Guid systemId, decimal systemAmount, List<ParentRow> ParentList)
        {
            dynamic outputList = null;
            using (DataConnection conn = new DataConnection())
            {
                outputList = (from output in conn.Get<LCA.Data.LCASystemOutput>()
                             join u in conn.Get<LCA.Data.Unit>() on output.Unit equals u.Id into joined
                             from j in joined.DefaultIfEmpty()
                             where output.RelatedSystem == systemId
                             select new
                             {
                                 RefItemType = output.RefItemType,
                                 RefItemId = output.RefItemId,
                                 Amount = output.Amount,
                                 Unit = (Guid?) j.Id
                             }).ToList();


                foreach (var item in outputList)
                {
                    if (item.RefItemType == "Flow")
                    {
                        ProcessImpactFactor_OnFlow(item.RefItemId, ((String)item.Amount).ToDecimal().Value, item.Unit, systemAmount, Source.Output, ParentList);
                    }
                    else if (item.RefItemType == "System")
                    {
                        Guid relatedSystemId = item.RefItemId;
                        LCA.Data.LCASystem system = conn.Get<LCA.Data.LCASystem>().Where(s => s.Id == relatedSystemId).FirstOrDefault();
                        Guid? systemUnitId = system.Unit;
                        decimal relativeAmount = ((String)item.Amount).ToDecimal().Value / system.Amount.ToDecimal().Value;
                        decimal multiplier = GetMultiplier_OnTargetUnit(conn, item.Unit, systemUnitId);

                        // Recursive Call. Watch this.
                        decimal SystemAmount = relativeAmount * multiplier * systemAmount;
                        ParentList.Add(new ParentRow { Source = Source.Output, ParentId = item.RefItemId });
                        ProcessSystem(item.RefItemId, SystemAmount, ParentList);

                        // Remove Last item
                        if (ParentList.Count > 0)
                            ParentList.Remove(ParentList.Last());
                    }

                }
            }


        }

        private void ProcessTransportA(Guid systemId, decimal systemAmount, List<ParentRow> ParentList)
        {
            dynamic taList = null;
            using (DataConnection conn = new DataConnection())
            {
                taList = (from ta in conn.Get<LCA.Data.LCASystemTransportA>()
                              join u in conn.Get<LCA.Data.Unit>() on ta.Unit equals u.Id into joined
                              from j in joined.DefaultIfEmpty()
                              where ta.RelatedSystem == systemId
                              select new
                              {
                                  flowId = ta.Flow,
                                  Amount = ta.Amount,
                                  Unit = (Guid?) j.Id
                              }).ToList();


            }

            foreach (var item in taList)
            {
                ProcessImpactFactor_OnFlow(item.flowId, ((String)item.Amount).ToDecimal().Value, item.Unit, systemAmount, Source.TransportA, ParentList);

            }
        }

        private void ProcessTransportB(Guid systemId, decimal systemAmount, List<ParentRow> ParentList)
        {
            //// Km
            //Guid DistanceBasedUnit = Guid.Parse("0440A2E0-AD6A-4EDB-99B4-C6CFBFA12409");
            //// ton
            //Guid KgLoadBasedUnit = Guid.Parse("5F10554D-8E05-4845-86F1-FB858B6FBFB9");

            dynamic tbList = null;
            using (DataConnection conn = new DataConnection())
            {
                tbList = (from tb in conn.Get<LCA.Data.LCASystemTransportB>()
                          join v in conn.Get<LCA.Data.HeavyVehicle>() on tb.Vehicle equals v.Id
                          //join f in conn.Get<LCA.Data.Flow>() on v.Flow equals f.Id
                          join u in conn.Get<LCA.Data.Unit>() on tb.DistanceUnit equals u.Id into joinedDU
                          from uu in joinedDU.DefaultIfEmpty()
                          join u1 in conn.Get<LCA.Data.Unit>() on tb.kgLoadUnit equals u1.Id into joinedKU
                          from uu1 in joinedKU.DefaultIfEmpty()
                          where tb.RelatedSystem == systemId
                          select new
                          {
                              // VehicleId = tb.Vehicle,
                              Distance = tb.Distance,
                              DistanceUnit = (Guid?) uu.Id,
                              KgLoad = tb.kgLoad,
                              KgLoadUnit = (Guid?) uu1.Id,
                              // HeavyVehicle
                              FullLoad = v.FullLoad,
                              PercentLoad = v.PercentLoad,
                              DepartedFlow = v.DepartedFlow,
                              ReturnedFlow = (Guid?)v.ReturnedFlow
                          }).ToList();


                foreach (var item in tbList)
                {
                    // Depart: Emission * kg Load * Distance
                    // BasedUnit Distance : km
                    // BasedUnit KgLoad : ton
                    decimal DistanceMul = GetMultiplier_OnTargetUnit(conn, item.DistanceUnit, AppSettings.TransportB_BasedUnit.Distance);
                    decimal KgLoadMul = GetMultiplier_OnTargetUnit(conn, item.KgLoadUnit, AppSettings.TransportB_BasedUnit.KgLoad);
                    decimal Amount = ((String)item.KgLoad).ToDecimal().Value * ((String) item.Distance).ToDecimal().Value * DistanceMul * KgLoadMul;
                    ProcessImpactFactor_OnFlow(item.DepartedFlow, Amount, null, systemAmount, Source.TransportB, ParentList);


                    // Return: (Emission * kg Load * Distance) / (Full Load * Percent Load)
                    // FullLoad: ton
                    // PercentLoad : [0,1]
                    if (item.ReturnedFlow != null)
                    {
                        decimal Divider = item.FullLoad * (item.PercentLoad / 100M);
                        decimal ReturnAmount = Amount / Divider;
                        ProcessImpactFactor_OnFlow(item.ReturnedFlow, ReturnAmount, null, systemAmount, Source.TransportB, ParentList);
                    }
                }

            }


            
        }

        public void ProcessImpactFactor_OnFlow(Guid flowId, decimal Amount, Guid? unitId, decimal systemAmount, Source source, List<ParentRow> ParentList)
        {
            // Process Impact Factor, on Flow
            
            dynamic factorList = null;
            LCA.Data.Flow flow = null;
            using (DataConnection conn = new DataConnection())
            {
                flow = conn.Get<LCA.Data.Flow>().Where(x => x.Id == flowId).FirstOrDefault();

                decimal multiplier = GetMultiplier_OnTargetUnit(conn, unitId, flow.Unit);

                factorList = (from ff in conn.Get<LCA.Data.FlowFactor>()
                                 join f in conn.Get<LCA.Data.Flow>() on ff.Flow equals f.Id
                                 where f.Id == flowId
                                 select new
                                 {
                                     Factor = ff.Factor,
                                     Emission = ff.Emission
                                 }).AsEnumerable()
                                 .Select((row, index) => new
                                 {
                                     Factor = row.Factor,
                                     Emission = row.Emission.ToDecimal() * Amount * multiplier * systemAmount
                                 }).ToList();


            }


            
            foreach (var item in factorList)
            {
                //// For Impact Assessment
                var factor = list.Where(x => x.FactorId == item.Factor).SingleOrDefault();
                if (factor != null)
                {
                    factor.Impact += item.Emission;

                }
                else
                {
                    ImpactFactorCalculationResult row = new ImpactFactorCalculationResult
                    {
                        FactorId = item.Factor,
                        FactorName = GetFactorName(item.Factor),
                        Impact = item.Emission,
                        UnitName = GetUnitName(item.Factor),
                        IndicatorId = GetIndicatorId(item.Factor)
                    };

                    list.Add(row);
                }



                //// For Hotspot
                HotspotRow HotspotRow = GetHotspotRow(flow, item.Emission, source, ParentList);

                var hotspotResultRow = hotspotList.Where(x => x.FactorId == item.Factor).SingleOrDefault();
                if (hotspotResultRow != null)
                {
                    hotspotResultRow.Rows.Add(HotspotRow);
                }
                else
                {
                    HotspotResult rowHotspot = new HotspotResult
                    {
                        FactorName = GetFactorName(item.Factor),
                        FactorId = item.Factor,
                        Rows = new List<HotspotRow>()
                    };
                    rowHotspot.Rows.Add(HotspotRow);
                    hotspotList.Add(rowHotspot);

                }
                
                    
            }

        }

        private HotspotRow GetHotspotRow(LCA.Data.Flow flow, decimal Emission, Source source, List<ParentRow> ParentList)
        {
            HotspotRow row = new HotspotRow();
            if (ParentList.Count > 0)
            {
                // Consider only first level
                string firstSource = ParentList[0].Source.ToString();
                string firstSystemId = ParentList[0].ParentId.ToString();
                
                row.ItemName = firstSystemId +"|"+ParentList.Count;
                row.ItemType = "System-Raw";
                row.Emission = Emission;
                row.Source = firstSource;
                row.ItemUrl = "";
            }
            else
            {
                row.ItemName = flow.Name;
                row.ItemType = "Flow";
                row.Emission = Emission;
                row.Source = source.ToString();
                row.ItemUrl = GetFriendlyUrl(flow.Name, "Flow", flow.FlowId);
            }

            return row;
        }
        
        private decimal GetMultiplier_OnTargetUnit(DataConnection conn, Guid? inputUnitId, Guid? targetUnitId)
        {
            LCA.Data.Unit inputUnit = conn.Get<LCA.Data.Unit>().Where(x => x.Id == inputUnitId).FirstOrDefault();
            LCA.Data.Unit targetUnit = conn.Get<LCA.Data.Unit>().Where(x => x.Id == targetUnitId).FirstOrDefault();

            //- No Fill >> Ignore
            if (inputUnit == null)
                return 1;

            //- Flow doesnt have Unit >> Ignore
            if (targetUnit == null)
                return 1;

            //- Fill, Incorrect Group >> Ignore
            // Impossible to change unit across the group
            if (inputUnit.UnitGroup != targetUnit.UnitGroup)
            {
                // Print some log, that UnitGroups are not matched
                return 1;
            }


            //- Fill, Correct Group >> Cal
            try
            {
                decimal decInputUnit = inputUnit.FactorAmount.ToDecimal().Value;
                decimal decTargetUnit = targetUnit.FactorAmount.ToDecimal().Value;

                decimal result = decInputUnit / decTargetUnit;

                return result;
            }
            catch (Exception ex)
            {
                // Print some logs here
                return 1;
            }

            // return 1;
        }

        public string GetFactorName(Guid factorId)
        {
            string result = "";
            using (DataConnection conn = new DataConnection())
            {
                var item = conn.Get<LCA.Data.Factor>().Where(x => x.Id == factorId).FirstOrDefault();
                if (item != null)
                    result = item.Name;
            }

            return result;
        }

        public string GetUnitName(Guid factorId)
        {
            string result = "";
            using (DataConnection conn = new DataConnection())
            {
                result = (from f in conn.Get<LCA.Data.Factor>()
                           join u in conn.Get<LCA.Data.Unit>() on f.Unit equals u.Id into joined
                           from j in joined.DefaultIfEmpty()
                           where f.Id == factorId
                           select j.Name).FirstOrDefault();
            }

            return result;
        }

        public string GetIndicatorId(Guid factorId)
        {
            string result = "";
            using (DataConnection conn = new DataConnection())
            {
                LCA.Data.Factor factor = conn.Get<LCA.Data.Factor>().Where(x => x.Id == factorId).FirstOrDefault();
                if(factor != null)
                    result = factor.Indicator.ToString();
            }

            return result;
        }

        private string GetSystemUrl(string Type, Guid ItemId)
        {
            string url = "";
            using (DataConnection conn = new DataConnection())
            {
                LCA.Data.LCASystem system = conn.Get<LCA.Data.LCASystem>().Where(x => x.Id == ItemId).FirstOrDefault();
                if (system != null)
                {
                    url = GetFriendlyUrl(system.Name, "System", system.LCASystemId);
                }
            }
            return url;
        }

        private string GetFriendlyUrl(string name, string type, int flowId)
        {
            // string title = HttpUtility.UrlEncode(GetUrlFromTitle(name).ToLower());
            return string.Format("/viewer/{0}/{1}", type, flowId.ToString());
        }

        public string GetUrlFromTitle(string title)
        {
            return Regex.Replace(title, @"[^\w\d ]+", string.Empty).Replace(" ", "-");

        }

        public List<ImpactFactorCalculationResult> GetResult()
        {
            return list;
        }

        public List<HotspotResult> GetHotspotList()
        {
            return hotspotList;
        }
    }
}
